#include "CutGUI.hpp"
#include <Renderer\Vis2DRenderer.hpp>
#include <Image\ImageDrawer.hpp>
#include <Graphics\ColorDefine.hpp>

namespace zzz {
CutGUI::CutGUI()
:left_(false), right_(false),
inputstatus_(FIRST_CLICK), optmode_(true), usemask_(false),
masksize_(10), boxsize_(5), curseg_(1)
{
  
}

bool CutGUI::OnMouseMove(unsigned int nFlags,int x,int y)
{
  x_ = x;
  y_ = y;

  if (ori_.size()==0) return false;
  pos_=Vector2ui(renderer_->GetHoverPixel(x,y));

  // If it is not inside image
  if (!show_.IsInside(pos_)) {
    opt_[0]=-1;
    return true;
  }

  // Draw mask
  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL)) {
    ImageDrawer<zuchar> drawer(mask_);
    // Draw
    if (left_) {
      vector<Vector2i> path;
      RasterizeLine(last_[0], last_[1], pos_[0], pos_[1], path);
      for (zuint i=0; i<path.size(); i++)
        drawer.FillCircle(1, path[i][0], path[i][1], masksize_);
      pathfinder_.SetMask(mask_);
      last_=pos_;
    } else if (right_) {
      // Erase
      vector<Vector2i> path;
      RasterizeLine(last_[0], last_[1], pos_[0], pos_[1], path);
      for (zuint i=0; i<path.size(); i++)
        drawer.FillCircle(0, path[i][0], path[i][1], masksize_);
      pathfinder_.SetMask(mask_);
      last_=pos_;
    }
    PrepareShow(MASK_MODE);
  } else if (CheckBit(nFlags,ZZZFLAG_SHIFT)) {  // straight line
    if (inputstatus_==FIRST_CLICK)
      opt_=pos_;
    else if (inputstatus_==REST_CLICK) {
      opt_=pos_;
      const Vector2ui &last(seeds_.back());
      vector<Vector2i> path;
      RasterizeLine(last[0], last[1], pos_[0], pos_[1], path);
      curpath_.clear();
      for (zuint i=0; i<path.size(); i++)
        curpath_.push_back(Vector2ui(path[i]));
    }
    PrepareShow(CUT_MODE);
  } else {  // snap
    if (inputstatus_==FIRST_CLICK) {
      if (optmode_) 
        opt_=pathfinder_.OptimizeClick(pos_);
    } else if (inputstatus_==REST_CLICK) {
      const Vector2ui firstpos=seeds_.front();
      // Check close loop
      if (seeds_.size()>=3 && 
        Abs((int)pos_[0]-(int)firstpos[0])<=boxsize_ && 
        Abs((int)pos_[1]-(int)firstpos[1])<=boxsize_)
        opt_=firstpos;
      // Optimize click
      else if (optmode_)
        opt_=pathfinder_.OptimizeClick(pos_);
      pathfinder_.FindPath(opt_,curpath_);
    }
    PrepareShow(CUT_MODE);
  }
  renderer_->Redraw();
  return true;
}

bool CutGUI::OnLButtonDown(unsigned int nFlags,int x,int y)
{
  left_=true;
  if (!show_.IsInside(pos_)) return false;
  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL)) {
    last_=pos_;
    OnMouseMove(nFlags,x,y);
  }
  return true;
}

bool CutGUI::OnLButtonUp(unsigned int nFlags,int x,int y)
{
  left_=false;
  if (ori_.size()==0) return false;  //not load, do nothing
  if (!show_.IsInside(pos_)) return false;
  // Drawing mask, do nothing
  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL)) {
    return false;
  }
  switch(inputstatus_) {
  case FIRST_CLICK:
    seeds_.clear();
    pathfinder_.SetStart(opt_);
    seeds_.push_back(opt_);
    inputstatus_=REST_CLICK;
    PrepareShow(CUT_MODE);
    break;
  case REST_CLICK:
    fixedpath_.push_back(curpath_);
    curpath_.clear();
    pathfinder_.SetStart(opt_);
    seeds_.push_back(opt_);
    if (seeds_.size()>=4 && opt_==seeds_.front()) {
      inputstatus_=FINISH_CLICK;
    }
    PrepareShow(CUT_MODE);
    break;
  case FINISH_CLICK:
    Imageuc cut;
    GetCurCut(cut);
    CombineSeg(cut, curseg_);
    fixedpath_.clear();
    seeds_.clear();
    inputstatus_=FIRST_CLICK;
    PrepareShow(CUT_MODE);
  }
  renderer_->Redraw();
  return true;
}

bool CutGUI::OnRButtonDown(unsigned int nFlags,int x,int y)
{
  Vis2DRenderer *r = reinterpret_cast<Vis2DRenderer*>(renderer_);
  right_=true;
  if (usemask_ && CheckBit(nFlags, ZZZFLAG_CTRL)) {
    if (!show_.IsInside(pos_)) return false;
    last_=pos_;
    OnMouseMove(nFlags,x,y);
  }
  return true;
}

bool CutGUI::OnRButtonUp(unsigned int nFlags,int x,int y)
{
  right_=false;
  if (ori_.size()==0) return false;
  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL)) {
    return false;
  }
  if (inputstatus_==REST_CLICK || inputstatus_==FINISH_CLICK) {
    seeds_.pop_back();
    if (seeds_.empty()) {
      inputstatus_=FIRST_CLICK;
      curpath_.clear();
    } else {
      fixedpath_.pop_back();
      pathfinder_.SetStart(seeds_.back());
      OnMouseMove(nFlags,x,y);
      inputstatus_=REST_CLICK;
    }
    PrepareShow(CUT_MODE);
    renderer_->Redraw();
  }
  return true;
}

bool CutGUI::OnKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags)
{
  if (ori_.size()==0) return false;
  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL))
    PrepareShow(MASK_MODE);
  else
    PrepareShow(CUT_MODE);
  renderer_->Redraw();
  return true;
}

bool CutGUI::OnKeyUp(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags)
{
  if (ori_.size()==0) return false;

  if (usemask_ && CheckBit(nFlags,ZZZFLAG_CTRL)) {
    if (nChar=='-') masksize_=Max(0, masksize_-1);
    if (nChar=='=') masksize_++;
    PrepareShow(MASK_MODE);
    renderer_->Redraw();
  } else {
    if (nChar=='-') boxsize_=Max(0, boxsize_-1);
    if (nChar=='=') boxsize_++;
    pathfinder_.optrange_=boxsize_;
    PrepareShow(CUT_MODE);
    renderer_->Redraw();
  }
  return true;
}

void CutGUI::PrepareShow(ShowMode showmode)
{
  const int offsetr[8]={0,-1,-1,-1,1,1,1,0};
  const int offsetc[8]={1,1,0,-1,1,0,-1,-1};

  //combine segment
  for (zuint i=0; i<show_.size(); i++)
    if (segments_[i]!=0)
      show_[i]=ori_.at(i) * *(ColorDefine::DistinctValue[segments_[i]]);
    else
      show_[i]=ori_.at(i);
//   Imageuc cut;
//   GetCurCut(cut);
//   for (zuint i=0; i<show_.size(); i++)
//     if (cut[i]!=CUT_OUTSIDE)
//       show_[i]=ori_.at(i) * *(ColorDefine::DistinctValue[curseg_]);


  //draw mask
  if (usemask_)
    for (zuint i=0; i<ori_.size(); i++)
      if (mask_[i])
        show_[i]*=0.8;

  ImageDrawer<Vector4f> drawer(show_);
  //draw path
  for (zuint i=0; i<fixedpath_.size(); i++) for (zuint j=0; j<fixedpath_[i].size(); j++)
    drawer.DrawPoint(Vector4f(0,0,1,1),show_.ToIndex(Vector2i(fixedpath_[i][j])));
  for (zuint j=0; j<curpath_.size(); j++)
    drawer.DrawPoint(Vector4f(1,0,0,1),show_.ToIndex(Vector2i(curpath_[j])));

  //draw mouse position
  {
    if (ori_.IsInside(pos_)) {
      if (showmode==CUT_MODE) {
        if (inputstatus_==FINISH_CLICK) return;
        if (optmode_)  //show optimize search box
          drawer.DrawBox(Vector4f(1,1,0,1),pos_[0]-boxsize_,pos_[1]-boxsize_,pos_[0]+boxsize_,pos_[1]+boxsize_);
        if (seeds_.size()>=3 && opt_==seeds_.front())
          drawer.DrawCircle(Vector4f(0,0,1,1),opt_[0],opt_[1],2);  //loop
        else
          drawer.DrawCross(Vector4f(1,1,0,1),opt_[0],opt_[1],2);
      } else if (showmode==MASK_MODE) {
        drawer.DrawCircle(Vector4f(0.8,0.8,0.8,1),pos_[0],pos_[1],masksize_);
      }
    }
  }
}

void CutGUI::Prepare(const Image4f &img)
{
  ori_=img;
  //SetCenter(ori_.Cols(),ori_.Rows());
  pathfinder_.Prepare(ori_);

  show_=img;

  seeds_.clear();
  fixedpath_.clear();
  curpath_.clear();
  inputstatus_=FIRST_CLICK;

  mask_.SetSize(img.Size());
  mask_.Zero();

  //all segment id init to 0
  segments_.SetSize(img.Size());
  segments_.Zero();
  PrepareShow(CUT_MODE);
}

void CutGUI::GetCurCut(Imageuc &saveimg)
{
  const zuchar CUT_UNKNOWN=255;
  const zuchar CUT_CHECKING=254;
  const zuchar CUT_CUR=253;
  saveimg.SetSize(ori_.Size());
  memset(saveimg.Data(),CUT_UNKNOWN,saveimg.size());
  // Draw path.
  ImageDrawer<zuchar> drawer(saveimg);
  for (zuint i=0; i<fixedpath_.size(); i++) for (zuint j=0; j<fixedpath_[i].size(); j++)
    drawer.DrawPoint(CUT_EDGE,saveimg.ToIndex(Vector2i(fixedpath_[i][j])));
  // Region growing.
  deque<Vector2ui> q;
  for (zuint r=0; r<saveimg.Rows(); r++) for(zuint c=0; c<saveimg.Cols(); c++)
  {
    if (saveimg(r,c)!=CUT_UNKNOWN) continue;

    bool mainRegion=true;
    //if region touches boundary of image, it is not mainRegion
    q.push_front(Vector2ui(r,c));
    saveimg(r,c)=CUT_CHECKING;
    while(!q.empty()) {
      Vector2ui p(q.back());
      q.pop_back();
      saveimg[p]=CUT_CUR;
      if (p[0]>0 && saveimg(p[0]-1,p[1])==CUT_UNKNOWN) {
        q.push_front(Vector2ui(p[0]-1,p[1]));
        saveimg(p[0]-1,p[1])=CUT_CHECKING;
      }
      if (p[0]<saveimg.Rows()-1 && saveimg(p[0]+1,p[1])==CUT_UNKNOWN) {
        q.push_front(Vector2ui(p[0]+1,p[1]));
        saveimg(p[0]+1,p[1])=CUT_CHECKING;
      }
      if (p[1]>0 && saveimg(p[0],p[1]-1)==CUT_UNKNOWN) {
        q.push_front(Vector2ui(p[0],p[1]-1));
        saveimg(p[0],p[1]-1)=CUT_CHECKING;
      }
      if (p[1]<saveimg.Cols()-1 && saveimg(p[0],p[1]+1)==CUT_UNKNOWN) {
        q.push_front(Vector2ui(p[0],p[1]+1));
        saveimg(p[0],p[1]+1)=CUT_CHECKING;
      }
      if (p[0]==0 || p[0]==saveimg.Rows()-1 || p[1]==0 || p[1]==saveimg.Cols()-1)
        mainRegion=false;
    }
    if (mainRegion) {
      for (zuint i=0; i<saveimg.size(); i++)
        if (saveimg[i]==CUT_CUR) saveimg[i]=CUT_INSIDE;
    } else {
      for (zuint i=0; i<saveimg.size(); i++)
        if (saveimg[i]==CUT_CUR) saveimg[i]=CUT_OUTSIDE;
    }
  }
}

void CutGUI::CombineSeg(Imageuc &cut, zuchar seg_id)
{
  for (zuint i=0; i<cut.size(); i++)
    if (cut[i]!=CUT_OUTSIDE) segments_[i]=seg_id;
}

};  // namespace zzz